package org.example.filter;

import com.google.common.base.Joiner;
import io.jsonwebtoken.Claims;
import org.example.utils.JwtUtils;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
 * 1.用户在后续的访问过程中，我们需要去验证 jwt的合法性；
 * 2.并且在该过滤器中，jwt校验合法后，要让 spring security 觉得我们登录了。
 * 3.那么如何就通过 jwt 就让 spring security 觉得我们登录呢? spring security就看
 * 自己的上下文对象中是否有一个 UsernamePasswordAuthenticationToken 这样一个对象。那么我们的处理
 * 就是往 spring security的上下文对象中塞上这么一个对象即可。
 *
 * 4.上下文对象? 这个概念不是局限某一项具体框架的概念，很多的框架中都有这么一个概念，所谓的上下文对象，就是从
 *  用户请求开始，知道其相应，自始至终有那么一个对象贯穿始终，在整个环节的过程，都可以通过各种方法能够拿到的一个对象。
 * 5.spring security这个框架中的上下文对象指的是，SecurityContext, 那么在任何位置都可以调用  SecurityContextHolder.getContext()
 * 获取到该对象。
 *
 * 在spring中用过滤器就使用 OncePerRequestFilter， 因为该过滤器是spring做了兼容性(兼容web2, web3, web4标准)的一个过滤器。
 */
@Component
public class JwtFilter extends OncePerRequestFilter {

    @Resource
    private JwtUtils jwtUtils;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String uri = request.getRequestURI();
        // 如果是登录
        if("/login".equals(uri)) {
            filterChain.doFilter(request, response);
            return;
        }
        /**
         * 1.用户在访问受限资源的时候，携带jwt，通过在请求头中使用 Authorization 来携带 jwt.
         * 2.请求头也是 key - value对的形式；
         * 3.Authorization 是标准的Http请求头中的一个参数。
         */
        String jwt = request.getHeader("Authorization");
        // null != jwt && !"".equals(jwt.trim()), jwt这个字符串不为null, 并且有值
        if(StringUtils.hasText(jwt)) {
            /**
             * 1.校验jwt的合法性；
             * 2.解析jwt, 得到一个 UsernamePasswordAuthenticationToken 这个类型的对象，塞到spring security的上下文对中
             */
            JwtUtils.JwtVerifyInfo verifyInfo = jwtUtils.verifyJwt(jwt);
            switch (verifyInfo.getJwtStatus()) {
                case correct: // 表示jwt验证正确
                    // 解析jwt, 得到一个 UsernamePasswordAuthenticationToken 这个类型的对象，塞到spring security的上下文中

                    Claims claims = verifyInfo.getClaims();
                    String name = claims.getSubject(); // 获取到用户名

                    List<String> perms = claims.get("perms", List.class);  // 获取到用户所有的权限

                    /**
                     * UsernamePasswordAuthenticationToken需要三个参数：
                     *   1.用户名，从jwt中获取即可。
                     *   2.密码，给个“”, 不能给null.
                     *   3.权限列表； 要求是这样一个集合 Collection<GrantedAuthority> cols;
                     *     这个集合 spring security提供了一个工具类：
                     *      Collection<GrantedAuthority> cols =
                     *          AuthorityUtils.commaSeparatedStringToAuthorityList("sys:user:delete,sys:user:edit,sys:dept:view")
                     *
                     *  Joiner.on(",").join(Arrays.asList("A", "B", "C")) => A,B,C
                     */
                    UsernamePasswordAuthenticationToken token =
                            new UsernamePasswordAuthenticationToken(name, "", AuthorityUtils
                                    .commaSeparatedStringToAuthorityList(Joiner.on(",").join(perms)));

                    // 将请求(request)信息设置到token, 因为spring security底层要用到请求信息
                    token.setDetails(request);
                    /**
                     * 1.SecurityContextHolder.getContext() 获取SpringSecurity的上下文对象；
                     * 2. SecurityContextHolder.getContext().setAuthentication(token); 将token设置到上下文对象中。
                     */
                    // 将 UsernamePasswordAuthenticationToken 对象设置到 spring security的上下文中
                    SecurityContextHolder.getContext().setAuthentication(token);
                    filterChain.doFilter(request, response);
                    break;
                /**
                case expired:
                    response.setStatus(401); //返回一个 401状态码，表示没有登录
                    break;
                case temper:
                    // 如果公司有风控系统，当前IP或者其他信息需要纳入到风控系统
                    response.setStatus(401); //返回一个 401状态码，表示没有登录
                    break;
                 */
                case expired:
                case temper:
                    // 如果公司有风控系统，当前IP或者其他信息需要纳入到风控系统
                    response.setStatus(401); //返回一个 401状态码，表示没有登录
                    break;
            }
        }else {  // 表示请求头中没有拿到jwt
            response.setStatus(401); //返回一个 401状态码，表示没有登录
        }
    }
}
